package app.server.project.main.service.service.luck;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.websocket.WSMgr;
import app.server.project.main.service.bo.event.CreateLuckOrderEvent;
import app.server.project.main.service.bo.event.RechargeSuccessEvent;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.constants.LuckType;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.user.UserActiveRewardDao;
import app.server.project.main.service.db.dao.user.UserInfoDao;
import app.server.project.main.service.db.dao.user.UserLuckOrderDao;
import app.server.project.main.service.db.entity.user.UserActiveRewardEntity;
import app.server.project.main.service.db.entity.user.UserLuckOrderEntity;
import app.server.project.main.service.dto.*;
import app.server.project.main.service.dto.race.RaceValDto;
import app.server.project.main.service.service.recharge.CallbackType;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArrayList;

@RequiredArgsConstructor
@Service
@Slf4j
public class BTCLuckService {

    private final static long Max = 100;
    private final UserLuckOrderDao userLuckOrderDao;
    private final UserActiveRewardDao userActiveRewardDao;

    private final GlobalCfgDao globalCfgDao;
    private final UserInfoDao userInfoDao;
    private ConcurrentLinkedDeque<UserLuckOrderEntity> deque = new ConcurrentLinkedDeque<>();
    private CopyOnWriteArrayList<UserLuckOrderEntity> successLst = new CopyOnWriteArrayList<>();
    private LocalDateTime nextTime;

    private static int countStr(String targetStr, String choiceStr) {
        var num = 0;
        for (int i = 0; i < targetStr.length(); i++) {
            var oneStr = targetStr.charAt(i);
            var oneChoiceStr = choiceStr.charAt(i);
            if (oneStr == oneChoiceStr) {
                num++;
            }
        }
        return num;
    }


    public BalanceDto getBalance(long userId) {
        UserActiveRewardEntity userEnergyEntity = userActiveRewardDao.getInfoById(userId);

        return BalanceDto.of(String.valueOf(userEnergyEntity.getTonVal()), String.valueOf(LuckType.TonRate));
    }

    @EventListener(CreateLuckOrderEvent.class)
    public void onCreateOrder(CreateLuckOrderEvent event) {
        if (event.getType() != LuckType.BTC) return;
        UserLuckOrderEntity userLuckOrderEntity = UserLuckOrderEntity.of(event.getOrderId());
        userLuckOrderEntity.setChoice(event.getChoice());
        userLuckOrderEntity.setUserId(event.getUserId());

        userLuckOrderEntity.setOrderType(event.getType());
        //刷新缓存
        userLuckOrderDao.updateOrder(userLuckOrderEntity);
    }

    @EventListener(RechargeSuccessEvent.class)
    public void onRechargeSuccess(RechargeSuccessEvent event) {
        if (event.getCallbackType() != CallbackType.BtcLuck) return;
        var order = userLuckOrderDao.getOrder(event.getOrder().getId());
        if (order.getId() == Def.Zero) return;
        if (order.getOrderType() != LuckType.BTC) return;
        //检查订单到账时间
        if (event.getCreateTime() > Def.Zero) {
            LocalDateTime localDateTime = Instant.ofEpochSecond(event.getCreateTime())
                    .atZone(ZoneId.systemDefault())
                    .toLocalDateTime();
            order.setSuccessTime(localDateTime);
            order.setSuccessUnixTime(event.getCreateTime());
        } else {
            order.setSuccessTime(LocalDateTime.now());
            order.setSuccessUnixTime(System.currentTimeMillis() / 1000);
        }

        //开始进入匹配队列
        deque.add(order);
        var lst = this.userLuckOrderDao.getAll(order.getUserId(), LuckType.BTC, Max);
        lst.removeIf(item -> item.getId() == order.getId());
        lst.add(Def.Zero, order);
        if (lst.size() > Max) {
            var temp = lst.subList((int) Max, lst.size());
            lst.removeAll(temp);
        }
    }

    @EventListener(value = {ApplicationReadyEvent.class})
    public void init() {
        var all = userLuckOrderDao.getAllUnSuccess(LuckType.BTC);
        this.deque.addAll(all);
        this.successLst.addAll(this.userLuckOrderDao.getAllLuck(LuckType.BTC, Max));
    }

    /**
     * 15秒查询一次区块,否则触发对方限流
     */
    @Scheduled(fixedDelay = 60 * 1000)
    public void flushBlockLst() {
        if (nextTime != null) {
            if (LocalDateTime.now().isBefore(nextTime)) {
                return;
            }
        }
        if (this.deque.isEmpty()) return;
        String lastUrl = "https://blockstream.info/api/blocks/tip/height";
        String lastHeight = HttpUtil.get(lastUrl, 100 * 1000);
        if (StrUtil.isBlank(lastHeight)) return;
        String lstUrl = "https://blockstream.info/api/blocks/" + lastHeight;
        String lst = HttpUtil.get(lstUrl, 100 * 1000);
        if (StrUtil.isBlank(lst)) return;
        if (!JSONUtil.isTypeJSON(lst)) {
            //5秒之后再试
            this.nextTime = LocalDateTime.now().plusSeconds(5);
            return;
        }
        var lstArr = JSONUtil.parseArray(lst);
        var lstDto = lstArr.toList(BtcDto.class);
        //var lstDto = new ArrayList<BtcDto>();
//        var btcDto = new BtcDto();
//        btcDto.setId("00000000000000000002109d4a8217c40912b632d92c226fa42d5e86460ac10d");
//        btcDto.setHeight(913848);
//        btcDto.setTimestamp(1644112000);
//        lstDto.add(btcDto);
        this.matchOrder(lstDto);
    }

    public List<MyLuckOrderDto> getMyAllBtc(long userId) {
        var lst = this.userLuckOrderDao.getAll(userId, LuckType.BTC, Max);
        return lst.stream().map(item -> {
            var ret = MyLuckOrderDto.of(item, LuckType.TonRate);
            if (ret.getOrder().getSuccessTime() != null) {
                ret.getOrder().setSuccessTimeStr(ret.getOrder().getSuccessTime().plusHours(4));
            }
            return ret;
        }).toList();
    }

    public List<CommonLuckOrderDto> getAllBtcCommon() {
        return successLst.stream().map(item -> {
            if (item.getSuccessTime() != null) {
                item.setSuccessTimeStr(item.getSuccessTime().plusHours(4));
            }
            return CommonLuckOrderDto.of(item, LuckType.TonRate, this.userInfoDao.getUserInfoById(item.getUserId()).getNick());
        }).toList();
    }

    public void matchOrder(List<BtcDto> lst) {
        var firstOrder = deque.pollFirst();
        if (firstOrder == null) return;
        int max = 100;
        var failLst = new ArrayList<UserLuckOrderEntity>();
        while (firstOrder != null && max > 0) {
            boolean match = false;
            for (int i = lst.size() - 1; i >= 0; i--) {
                var btcDto = lst.get(i);
                if (btcDto.getTimestamp() >= firstOrder.getSuccessUnixTime()) {
                    var flag = this.matchChoice(firstOrder, btcDto);
                    var allOrders = this.userLuckOrderDao.getAll(firstOrder.getUserId(), LuckType.BTC, Max);
                    UserLuckOrderEntity finalFirstOrder = firstOrder;
                    allOrders.removeIf(item -> item.getId() == finalFirstOrder.getId());
                    allOrders.add(Def.Zero, firstOrder);
                    match = true;
                    this.successLst.removeIf(item -> item.getId() == finalFirstOrder.getId());
                    this.successLst.add(Def.Zero, firstOrder);
                    if (flag) {

                    } else {
                        this.doSmallReward(firstOrder);
                    }
                    break;
                }
            }
            if (!match) {
                failLst.add(firstOrder);
            }
            firstOrder = deque.pollFirst();
            max--;
        }
        //失败的订单重新加入队列
        deque.addAll(failLst);
    }

    private void pushTon(long userId) {
        UserActiveRewardEntity rewardEntity = this.userActiveRewardDao.getInfoById(userId);
        var dto = RaceValDto.ofTon(rewardEntity.getTonVal(), LuckType.TonRate);
        WSMgr.push(userId, TcpCmd.TonVal, dto);
    }

    private void pushReward(long userId, long val, String hash, String choice, int rewardType) {
        var dto = LuckDto.of(String.valueOf(val), String.valueOf(LuckType.TonRate), hash, choice, rewardType);
        WSMgr.push(userId, TcpCmd.BtcReward, dto);
    }

    public boolean matchChoice(UserLuckOrderEntity order, BtcDto btcDto) {
        order.setHighVal(btcDto.getHeight());
        order.setHash(btcDto.getId());
        //修改订单状态
        order.setStatus(Def.True);
        //中奖字符串
        String btcStr = btcDto.getId().substring(btcDto.getId().length() - LuckType.BTCLen);
        //中了大奖
        UserActiveRewardEntity rewardEntity = this.userActiveRewardDao.getInfoById(order.getUserId());
        var num = countStr(btcStr, order.getChoice());
        if (num > 1) {
            switch (num) {
                case 4: {
                    //记录一下这个人真中了比特币
                    rewardEntity.addBtcVal(1);
                    return true;
                }
                case 3: {
                    doThreeReward(order, rewardEntity);
                    return true;
                }
                case 2: {
                    doTwoReward(order, rewardEntity);
                    return true;
                }
            }
        }
        for (int i = 0; i < order.getChoice().length(); i++) {
            var oneStr = order.getChoice().charAt(i);
            if (btcStr.contains(oneStr + "")) {
                long oneVal = 5 * LuckType.TonRate / 10;
                if (this.globalCfgDao.has(GlobalCfgKey.Luck_BTC_One)) {
                    oneVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Luck_BTC_One).getLong();
                }
                rewardEntity.addTonVal(oneVal);
                order.setTonVal(oneVal);
                order.setRewardType(4);
                pushReward(order.getUserId(), oneVal, btcDto.getId(), order.getChoice(), 4);
                pushTon(order.getUserId());
                return true;
            }
        }
        return false;
    }

    private boolean doTwoReward(UserLuckOrderEntity order, UserActiveRewardEntity rewardEntity) {
        long twoVal = 10 * LuckType.TonRate;
        if (this.globalCfgDao.has(GlobalCfgKey.Luck_BTC_Two)) {
            twoVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Luck_BTC_Two).getLong();
        }
        rewardEntity.addTonVal(twoVal);
        rewardEntity.addTonVal(twoVal);
        order.setTonVal(twoVal);
        order.setRewardType(3);
        WSMgr.push(order.getUserId(), TcpCmd.TonVal, rewardEntity.getTonVal());
        pushReward(order.getUserId(), twoVal, order.getHash(), order.getChoice(), 3);
        return true;
    }

    private boolean doThreeReward(UserLuckOrderEntity order, UserActiveRewardEntity rewardEntity) {
        long twoVal = 20 * LuckType.TonRate;
        if (this.globalCfgDao.has(GlobalCfgKey.Luck_BTC_Three)) {
            twoVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Luck_BTC_Three).getLong();
        }
        rewardEntity.addTonVal(twoVal);
        order.setTonVal(twoVal);
        order.setRewardType(2);
        this.successLst.add(order);
        this.pushTon(order.getUserId());
        this.pushReward(order.getUserId(), twoVal, order.getHash(), order.getChoice(), 2);
        return true;
    }

    private void doSmallReward(UserLuckOrderEntity order) {
        long smallVal = LuckType.TonRate / 10;
        if (this.globalCfgDao.has(GlobalCfgKey.Luck_BTC_Small)) {
            smallVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Luck_BTC_Small).getLong();
        }
        UserActiveRewardEntity rewardEntity = this.userActiveRewardDao.getInfoById(order.getUserId());
        rewardEntity.addTonVal(smallVal);
        order.setTonVal(smallVal);
        order.setRewardType(0);
        this.pushTon(order.getUserId());
        WSMgr.push(order.getUserId(), TcpCmd.BtcReward, LuckDto.of(String.valueOf(order.getTonVal()),
                String.valueOf(LuckType.TonRate), order.getHash(), order.getChoice(), 0));
    }
}
